#!/usr/bin/env python

import sys
import time
import serial
from threading import Thread

class RawInput:
	"""Gets a single character from standard input.  Does not echo to the screen."""
	def __init__(self):
		try:
			self.impl = RawInputWindows()
		except ImportError:
			self.impl = RawInputUnix()

	def __call__(self): return self.impl()


class RawInputUnix:
	def __init__(self):
		import tty, sys

	def __call__(self):
		import sys, tty, termios
		fd = sys.stdin.fileno()
		old_settings = termios.tcgetattr(fd)
		try:
			tty.setraw(sys.stdin.fileno())
			ch = sys.stdin.read(1)
		finally:
			termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
		return ch


class RawInputWindows:
	def __init__(self):
		import msvcrt

	def __call__(self):
		import msvcrt
		return msvcrt.getch()

class Baudrate:

	VERSION = '1.0'
	READ_TIMEOUT = 5
	BAUDRATES = [
#			"1200",
#			"1800",
#			"2400",
#			"4800",
			"9600",
			"38400",
			"19200",
			"57600",
			"115200",
	]

	UPKEYS = ['u', 'U', 'A']
	DOWNKEYS = ['d', 'D', 'B']

	MIN_CHAR_COUNT = 25
	WHITESPACE = [' ', '\t', '\r', '\n']
	PUNCTUATION = ['.', ',', ':', ';', '?', '!']
	VOWELS = ['a', 'A', 'e', 'E', 'i', 'I', 'o', 'O', 'u', 'U']

	def __init__(self, port=None, threshold=MIN_CHAR_COUNT, timeout=READ_TIMEOUT, name=None, auto=True, verbose=False):
		self.port = port
		self.threshold = threshold
		self.timeout = timeout
		self.name = name
		self.auto_detect = auto
		self.verbose = verbose
		self.index = len(self.BAUDRATES) - 1
		self.valid_characters = []
		self.ctlc = False
		self.thread = None
	
		self._gen_char_list()

	def _gen_char_list(self):
		c = ' '

		while c <= '~':
			self.valid_characters.append(c)
			c = chr(ord(c) + 1)
		
		for c in self.WHITESPACE:
			if c not in self.valid_characters:
				self.valid_characters.append(c)

	def _print(self, data):
		if self.verbose:
			sys.stderr.write(data)

	def Open(self):
		self.serial = serial.Serial(self.port, timeout=self.timeout)
		self.NextBaudrate(0)
		
	def NextBaudrate(self, updn):
		
		self.index += updn

		if self.index >= len(self.BAUDRATES):
			self.index = 0
		elif self.index < 0:
			self.index = len(self.BAUDRATES) - 1

		sys.stderr.write('\n\n@@@@@@@@@@@@@@@@@@@@@ Baudrate: %s @@@@@@@@@@@@@@@@@@@@@\n\n' % self.BAUDRATES[self.index])

		self.serial.flush()
		self.serial.baudrate = self.BAUDRATES[self.index]
		self.serial.flush()

	def Detect(self):
		count = 0
		whitespace = 0
		punctuation = 0
		vowels = 0
		start_time = 0
		timed_out = False
		clear_counters = False

		if not self.auto_detect:
			self.thread = Thread(None, self.HandleKeypress, None, (self, 1))
			self.thread.start()

		while True:
			if start_time == 0:
				start_time = time.time()

			byte = self.serial.read(1)
			
			if byte:
				if self.auto_detect and byte in self.valid_characters:
					if byte in self.WHITESPACE:
						whitespace += 1
					elif byte in self.PUNCTUATION:
						punctuation += 1
					elif byte in self.VOWELS:
						vowels += 1
					
					count += 1
				else:
					clear_counters = True

				self._print(byte)

				if count >= self.threshold and whitespace > 0 and punctuation > 0 and vowels > 0:
					break
				elif (time.time() - start_time) >= self.timeout:
					timed_out = True
			else:
				timed_out = True

			if timed_out and self.auto_detect:
				start_time = 0
				self.NextBaudrate(-1)
				clear_counters = True
				timed_out = False

			if clear_counters:
				whitespace = 0
				punctuation = 0
				vowels = 0
				count = 0
				clear_counters = False

			if self.ctlc:
				break

		self._print("\n")
		return self.BAUDRATES[self.index]

	def HandleKeypress(self, *args):
		userinput = RawInput()

		while not self.ctlc:
			c = userinput()
			if c in self.UPKEYS:
				self.NextBaudrate(1)
			elif c in self.DOWNKEYS:
				self.NextBaudrate(-1)
			elif c == '\x03':
				self.ctlc = True

	def MinicomConfig(self, name=None):
		success = True

		if name is None:
			name = self.name

		config =  "########################################################################\n"
		config += "# Minicom configuration file - use \"minicom -s\" to change parameters.\n"
		config += "pu port             %s\n" % self.port
		config += "pu baudrate         %s\n" % self.BAUDRATES[self.index]
		config += "pu bits             8\n"
		config += "pu parity           N\n"
		config += "pu stopbits         1\n"
		config += "pu rtscts           No\n"
		config += "########################################################################\n"

		if name is not None and name:
			try:
				open("/etc/minicom/minirc.%s" % name, "w").write(config)
			except Exception, e:
				print "Error saving minicom config file:", str(e)
				success = False

		return (success, config)

	def Close(self):
		self.ctlc = True
		self.serial.close()



if __name__ == '__main__':
	
	import subprocess
	from getopt import getopt as GetOpt, GetoptError

	def usage():
		baud = Baudrate()

		print ""
		print "Baudrate v%s" % baud.VERSION
		print "Craig Heffner, http://www.devttys0.com"
		print ""
		print "Usage: %s [OPTIONS]" % sys.argv[0]
		print ""
		print "\t-p <serial port>       Specify the serial port to use [/dev/ttyUSB0]"
		print "\t-t <seconds>           Set the timeout period used when switching baudrates in auto detect mode [%d]" % baud.READ_TIMEOUT
		print "\t-c <num>               Set the minimum ASCII character threshold used during auto detect mode [%d]" % baud.MIN_CHAR_COUNT
		print "\t-n <name>              Save the resulting serial configuration as <name> and automatically invoke minicom (implies -a)"
		print "\t-a                     Enable auto detect mode"
		print "\t-b                     Display supported baud rates and exit"
		print "\t-q                     Do not display data read from the serial port"
		print "\t-h                     Display help"
		print ""
		sys.exit(1)

	def main():
		display = False
		verbose = True
		auto = False
		run = False
		threshold = 25
		timeout = 5
		name = None
		port = '/dev/ttyUSB0'

		try:
			(opts, args) = GetOpt(sys.argv[1:], 'p:t:c:n:abqh')
		except GetoptError, e:
			print e
			usage()

		for opt, arg in opts:
			if opt == '-t':
				timeout = int(arg)
			elif opt == '-c':
				threshold = int(arg)
			elif opt == '-p':
				port = arg
			elif opt == '-n':
				name = arg
				auto = True
				run = True
			elif opt == '-a':
				auto = True
			elif opt == '-b':
				display = True
			elif opt == '-q':
				verbose = False
			else:
				usage()

		baud = Baudrate(port, threshold=threshold, timeout=timeout, name=name, verbose=verbose, auto=auto)

		if display:
			print ""
			for rate in baud.BAUDRATES:
				print "\t%s" % rate
			print ""
		else:
			print ""
			print "Starting baudrate detection on %s, turn on your serial device now." % port
			print "Press Ctl+C to quit."
			print ""

			baud.Open()
	
			try:
				rate = baud.Detect()
				print "\nDetected baudrate: %s" % rate
				
				if name is None:
					print "\nSave minicom configuration as: ",
					name = sys.stdin.readline().strip()
					print ""

				(ok, config) = baud.MinicomConfig(name)
				if name and name is not None:
					if ok:
						if not run:
							print "Configuration saved. Run minicom now [n/Y]? ",
							yn = sys.stdin.readline().strip()
							print ""
							if yn == "" or yn.lower().startswith('y'):
								run = True
					
						if run:
							subprocess.call(["minicom", name])
					else:
						print config
				else:
					print config
			except KeyboardInterrupt:
				pass
	
			baud.Close()

	main()
